﻿using System;
using System.Globalization;
using System.Text;
using System.Web;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Diagnostics;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.WebControls;
using Trentacular.SharePoint.Reflection;
using Trentacular.SharePoint.Util;

namespace Trentacular.Office.Server.Search.FacetedSearch.QueryRetrieval
{
    /// <summary>
    /// Serves as a wrapper class to the SearchResultsHiddenObject class that is
    /// internal to the Microsoft.Office.Server.Search assembly.  Properties and
    /// methods are exposed through this wrapper using reflection.
    /// </summary>
    public class MOSS2007QueryRetriever : ReflectingObjectWrapper, IQueryRetriever
    {
        private HttpContext context;

        public MOSS2007QueryRetriever(HttpContext context)
        {
            this.context = context;
        }

        private object _searchResultsHiddenObject;

        /// <summary>
        /// Gets or sets the wrapped search results hidden object.
        /// </summary>
        /// <value>The wrapped search results hidden object.</value>
        protected override object Target
        {
            get
            {
                if (_searchResultsHiddenObject == null)
                {
                    string key = "OSSSRHDC_" + ((int)QueryId.Query1);
                    _searchResultsHiddenObject = this.context.Items[key];

                    if (_searchResultsHiddenObject == null)
                        throw new InvalidOperationException(
                            "Failed to retrieve the query from the Http context."
                            );
                }

                return _searchResultsHiddenObject;
            }
        }

        /// <summary>
        /// Clears cached property values.
        /// </summary>
        public void ClearCachedValues()
        {
            _keywordQuery = null;
            _fullTextQuery = null;
        }

        private string _keywordQuery;

        /// <summary>
        /// Gets the keyword query.
        /// </summary>
        /// <value>The keyword query.</value>
        public string KeywordQuery
        {
            get
            {
                if (_keywordQuery == null)
                    _keywordQuery = GetPublicPropertyValue<string>("KeywordQuery");
                return _keywordQuery;
            }
        }

        private string _fullTextQuery;

        /// <summary>
        /// Gets the full text query.
        /// </summary>
        /// <value>The full text query.</value>
        public string FullTextQuery
        {
            get
            {
                if (_fullTextQuery == null)
                    _fullTextQuery = GetPublicPropertyValue<string>("FullTextQuery");
                return _fullTextQuery;
            }
        }

        private bool IsKeywordQuery
        {
            get { return string.IsNullOrEmpty(FullTextQuery); }
        }

        private string _queryLanguage;
        private string QueryLanguage
        {
            get
            {
                if (_queryLanguage == null)
                    _queryLanguage = GetPublicPropertyValue<string>("QueryLanguage");
                return _queryLanguage;
            }
        }

        private void SetKeywordQueryProperties(KeywordQuery query)
        {
            InvokeNonPublicInstanceMethod("SetKeywordQueryProperties", query);
        }

        private void SetQueryProperties(Query query)
        {
            InvokeNonPublicInstanceMethod("SetQueryProperties", query);
        }

        #region IQueryRetriever Implementation

        /// <summary>
        /// Retrieves the query that will mimic the query used to execute the main
        /// search but with the select clause replaced the given managed properties.
        /// </summary>
        /// <param name="managedPropertiesToQuery">An array of managed property names
        /// to include in the select clause of the query.</param>
        /// <returns>
        /// A query that will mimic the query used to execute the main search
        /// but with the select clause replaced the given managed properties.  Returns
        /// null when there is no query text.
        /// </returns>
        public Query RetrieveQuery(string[] managedPropertiesToQuery)
        {
            var query = BuildQuery(managedPropertiesToQuery);

            if (query == null)
                return null;

            // Set the culture for the query
            query.Culture = GetCulture();

            // Set the query properties on the query
            // from the search results hidden object
            SetQueryProperties(query);

            return query;
        }

        public string QueryText
        {
            get
            {
                if (this.IsKeywordQuery)
                    return this.KeywordQuery;

                return this.FullTextQuery;
            }
        }

        #endregion

        #region Query Helpers

        private CultureInfo GetCulture()
        {
            try
            {
                var queryLanguage = this.QueryLanguage;
                if (!string.IsNullOrEmpty(queryLanguage))
                    return new CultureInfo(queryLanguage);

                var request = this.context.Request;
                if (request.UserLanguages != null && request.UserLanguages.Length > 0)
                    return new CultureInfo(request.UserLanguages[0]);
            }
            catch (Exception e)
            {
                // TODO: How should this get logged?
                PortalLog.LogString(
                    "Ignoring exception in CultureInfo ctor: {0}",
                    e.ToString()
                    );
            }

            return CultureInfo.CurrentCulture;
        }

        private Query BuildQuery(string[] managedPropertiesToQuery)
        {
            if (this.IsKeywordQuery)
            {
                var keywordQuery = new KeywordQuery(ServerContext.Current);
                SetKeywordQueryProperties(keywordQuery);

                // Return null when there is no keyword
                if (string.IsNullOrEmpty(keywordQuery.QueryText))
                    return null;

                // Set the Select Properties to those of our facets
                keywordQuery.SelectProperties.Clear();
                keywordQuery.SelectProperties.AddRange(managedPropertiesToQuery);

                // Always sort by rank descending
                keywordQuery.SortList.Clear();
                keywordQuery.SortList.Add("Rank", SortDirection.Descending);

                return keywordQuery;
            }

            // Full Text Query
            var fullTextQuery = new FullTextSqlQuery(ServerContext.Current);
            var queryText = this.FullTextQuery;

            // Replace the select clause with the necessary columns for the facet
            // query, as well as append the sort
            var fromClauseIndex = queryText.IndexOf(" from scope()");
            var fromAndWhereClause = queryText.Substring(fromClauseIndex);
            var queryTextBuilder = new StringBuilder("SELECT ")
                .Append(managedPropertiesToQuery.Delimit(", "))
                .Append(fromAndWhereClause)
                .Append(" Order By Rank desc");

            fullTextQuery.QueryText = queryTextBuilder.ToString();
            fullTextQuery.KeywordInclusion = KeywordInclusion.AnyKeyword;

            return fullTextQuery;
        }

        #endregion
    }
}
